/* @flow */

import { isRegExp, remove } from "shared/util";
import { getFirstComponentChild } from "core/vdom/helpers/index";

type VNodeCache = { [key: string]: ?VNode };

function getComponentName(opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag);
}

function matches(
  pattern: string | RegExp | Array<string>,
  name: string
): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1;
  } else if (typeof pattern === "string") {
    return pattern.split(",").indexOf(name) > -1;
  } else if (isRegExp(pattern)) {
    return pattern.test(name);
  }
  /* istanbul ignore next */
  return false;
}

function pruneCache(keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance;
  for (const key in cache) {
    const cachedNode: ?VNode = cache[key];
    if (cachedNode) {
      const name: ?string = getComponentName(cachedNode.componentOptions);
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode);
      }
    }
  }
}

function pruneCacheEntry(
  cache: VNodeCache,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const cached = cache[key];
  if (cached && (!current || cached.tag !== current.tag)) {
    cached.componentInstance.$destroy();
  }
  cache[key] = null;
  remove(keys, key);
}

const patternTypes: Array<Function> = [String, RegExp, Array];

export default {
  name: "keep-alive", // keep-alive 本身没有任何功能
  abstract: true, // 抽象组件 特点就是没有 $parent $children 不会被记录在父子关系里面

  props: {
    include: patternTypes, // 哪些需要缓存
    exclude: patternTypes, // 哪些不需要缓存
    max: [String, Number], // 最大缓存多少个
  },

  created() {
    this.cache = Object.create(null); // 一个缓存列表
    this.keys = []; // 需要缓存几个 根据组件的key来缓存
  },

  destroyed() {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    this.$watch("include", (val) => {
      pruneCache(this, (name) => matches(val, name));
    });
    this.$watch("exclude", (val) => {
      pruneCache(this, (name) => !matches(val, name));
    });
  },

  render() {
    const slot = this.$slots.default; // 获取keep-alive中的插槽
    const vnode: VNode = getFirstComponentChild(slot); // 只会缓存插槽中的第一个组件
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions; // componentOptions ={Ctor,name,children}
    // componentInstnace = el
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions); //
      const { include, exclude } = this;
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode; // 如果不用缓存 直接返回即可
      }

      // 开始缓存
      const { cache, keys } = this;
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            // 1::my
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      if (cache[key]) {
        // 缓存的是实例 不是虚拟节点
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key); // 删除keys中有的key  在把最新的放入
        keys.push(key);
      } else {
        cache[key] = vnode; // 缓存vnode
        keys.push(key); // 放到缓存中
        // prune oldest entry  删除最久的
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // 在渲染的时候 就可以看有没有keep-alive
      vnode.data.keepAlive = true; // 当前这个虚拟节点是keep-alive
    }
    // 返回虚拟节点
    return vnode || (slot && slot[0]); // 之后正常渲染 ， 如果渲染过 会对这个实例继续复用

    //_vnode  div  $vnode 组件的本 my
  },
};
